<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 
<html>
<head>
<title>Drawing with Cairo II in GTK#</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="GUI, GTK#, Cairo, drawing, C#, programming, Linux">
<meta name="description" content="In this part of the GTK# tutorial we continue
drawing with Cairo library.">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container2">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content2">


<a href="/" title="Home">Home</a>&nbsp;
<a href="..">Contents</a>


<h1>Drawing with Cairo II</h1>

<p>
In this part of the GTK# programming tutorial, we will continue drawing with the Cairo library.
</p>

<div class="big_hor">
<script type="text/javascript"><!--
google_ad_client = "ca-pub-9706709751191532";
/* big_horizontal */
google_ad_slot = "2904953388";
google_ad_width = 728;
google_ad_height = 90;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>


<h2>Donut</h2>

<p>
In the following example we create an complex shape by rotating a bunch of ellipses. 
</p>

<div class="codehead">donut.cs</div> 
<pre class="code">
using Gtk;
using Cairo;
using System;
 
class SharpApp : Window {
 

    public SharpApp() : base("Donut")
    {
        SetDefaultSize(350, 250);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };
        
        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);
                
        cr.LineWidth = 0.5;

        int width, height;
        width = Allocation.Width;
        height = Allocation.Height;
       
        cr.Translate(width/2, height/2);
        cr.Arc(0, 0, 120, 0, 2*Math.PI);
        cr.Stroke();
         
        cr.Save();

        for (int i = 0; i &lt; 36; i++) {
            cr.Rotate( i*Math.PI/36);
            cr.Scale(0.3, 1);
            cr.Arc(0, 0, 120, 0, 2*Math.PI);
            cr.Restore();
            cr.Stroke();
            cr.Save();
        }

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}
</pre>

<p>
In this example, we create a donut. The shapes resembles a cookie, hence the name donut.
</p>

<pre class="explanation">
cr.Translate(width/2, height/2);
cr.Arc(0, 0, 120, 0, 2*Math.PI);
cr.Stroke();
</pre>

<p>
In the beginning there is an ellipse. 
</p>

<pre class="explanation">
for (int i = 0; i &lt; 36; i++) {
    cr.Rotate( i*Math.PI/36);
    cr.Scale(0.3, 1);
    cr.Arc(0, 0, 120, 0, 2*Math.PI);
    cr.Restore();
    cr.Stroke();
    cr.Save();
}
</pre>

<p>
After several rotations, there is a donut.
</p>

<img src="/img/gui/sharpgtk/donut.jpg" alt="Donut">
<div class="figure">Figure: Donut</div>


<h2>Gradients</h2>

<p>
In computer graphics, gradient is a smooth blending of shades from 
light to dark or from one color to another. In 2D drawing programs 
and paint programs, gradients are used to create colorful backgrounds and
special effects as well as to simulate lights and shadows. (answers.com) 
</p>

<div class="codehead">gradients.cs</div> 
<pre class="code">
using Gtk;
using Cairo;
using System;
 
class SharpApp : Window {
 

    public SharpApp() : base("Gradients")
    {
        SetDefaultSize(340, 390);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };
        
        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);
                
               
        LinearGradient lg1 = new LinearGradient(0.0, 0.0, 350.0, 350.0);
       
        int count = 1;

        for (double j=0.1; j<1.0; j+= 0.1) {
            if (Convert.ToBoolean(count % 2)) {
                lg1.AddColorStop(j, new Color(0, 0, 0, 1));
            } else {
                lg1.AddColorStop(j, new Color(1, 0, 0, 1));
            }
        count++;
        }

        cr.Rectangle(20, 20, 300, 100);
        cr.Pattern = lg1;
        cr.Fill();

        LinearGradient lg2 = new LinearGradient(0.0, 0.0, 350.0, 0);
       
        count = 1;

        for (double i=0.05; i<0.95; i+= 0.025) {
            if (Convert.ToBoolean(count % 2)) {
                lg2.AddColorStop(i, new Color(0, 0, 0, 1));
            } else {
                lg2.AddColorStop(i, new Color(0, 0, 1, 1));
            }
        count++;
        }

        cr.Rectangle(20, 140, 300, 100);
        cr.Pattern = lg2;
        cr.Fill();

        LinearGradient lg3 = new LinearGradient(20.0, 260.0,  20.0, 360.0);
        lg3.AddColorStop(0.1, new Color (0, 0, 0, 1) );
        lg3.AddColorStop(0.5, new Color (1, 1, 0, 1) );
        lg3.AddColorStop(0.9, new Color (0, 0, 0, 1) );

        cr.Rectangle(20, 260, 300, 100);
        cr.Pattern = lg3;
        cr.Fill();
        

        lg1.Destroy();
        lg2.Destroy();
        lg3.Destroy();        

        ((IDisposable) cr.Target).Dispose ();                                      
        ((IDisposable) cr).Dispose ();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}
</pre>

<p>
In our example, we draw three rectangles with three different gradients. 
</p>

<pre class="explanation">
LinearGradient lg1 = new LinearGradient(0.0, 0.0, 350.0, 350.0);
</pre>

<p>
Here we create a linear gradient pattern. The parameters specify the line, 
along which we draw the gradient. In our case it is a vertical line. 
</p>

<pre class="explanation"> 
LinearGradient lg3 = new LinearGradient(20.0, 260.0,  20.0, 360.0);
lg3.AddColorStop(0.1, new Color (0, 0, 0, 1) );
lg3.AddColorStop(0.5, new Color (1, 1, 0, 1) );
lg3.AddColorStop(0.9, new Color (0, 0, 0, 1) );
</pre>

<p>
We define color stops to produce our gradient pattern. In this case, 
the gradient is a blending of black and yellow colors. By adding two black 
and one yellow stops, we create a horizontal gradient pattern. What do these 
stops actually mean? In our case, we begin with black color, which will 
stop at 1/10 of the size. Then we begin to gradually paint in yellow, which will 
culminate at the centre of the shape. The yellow color stops at 9/10 of the size, 
where we begin painting in black again, until the end. 
</p>

<img src="/img/gui/sharpgtk/gradients.png" alt="Gradients">
<div class="figure">Figure: Gradients</div>


<h2>Puff</h2>

<p>
In the following example, we create a puff effect. The example will display a growing centered text,
that will gradully fade out from some point. This is a very common effect, 
which you can often see in flash animations. 
</p>

<div class="codehead">puff.cs</div> 
<pre class="code">
using Gtk;
using Cairo;
using System;
 
class SharpApp : Window {
 

    private bool timer = true;
    private double alpha = 1.0;
    private double size = 1.0;
    private DrawingArea darea;
   
    
    public SharpApp() : base("Puff")
    {
        SetDefaultSize(350, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };
        
        GLib.Timeout.Add(14, new GLib.TimeoutHandler(OnTimer));
        
        darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }
    
    bool OnTimer() 
    { 
        if (!timer) return false;

        darea.QueueDraw();
        return true;
    }      

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);
              
        int x = Allocation.Width / 2;
        int y = Allocation.Height / 2;

        cr.SetSourceRGB(0.5, 0, 0);
        cr.Paint();

        cr.SelectFontFace("Courier", FontSlant.Normal, FontWeight.Bold);

        size += 0.8;

        if (size > 20) {
            alpha -= 0.01;
        }

        cr.SetFontSize(size);
        cr.SetSourceRGB(1, 1, 1); 
        
        TextExtents extents = cr.TextExtents("ZetCode");

        cr.MoveTo(x - extents.Width/2, y);
        cr.TextPath("ZetCode");
        cr.Clip();
        cr.Stroke();
        cr.PaintWithAlpha(alpha);

        if (alpha &lt;= 0) {
            timer = false;
        }
        
        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}
</pre>

<p>
The example creates a growing and fading text on the window.
</p>

<pre class="explanation">
GLib.Timeout.Add(14, new GLib.TimeoutHandler(OnTimer));
</pre>

<p>
Every 14 ms the <code>OnTimer()</code> method is called.  
</p>

<pre class="explanation">
bool OnTimer() 
{ 
    if (!timer) return false;
    
    darea.QueueDraw();
    return true;
}      
</pre>

<p>
In the <code>OnTimer()</code> method, we call 
the <code>QueueDraw()</code> method upon the drawing
area, which triggers the <code>ExposeEvent</code>.
</p>

<pre class="explanation">
int x = Allocation.Width / 2;
int y = Allocation.Height / 2;
</pre>

<p>
Coordinates of the middle point. 
</p>

<pre class="explanation">
cr.SetSourceRGB(0.5, 0, 0);
cr.Paint();
</pre>

<p>
We set the background color to dark red color. 
</p>

<pre class="explanation">
size += 0.8;
</pre>

<p>
Each cycle, the font size will grow by 0.8 units.
</p>

<pre class="explanation">
if (size > 20) {
    alpha -= 0.01;
}
</pre>

<p>
The fading out begins after the font size is bigger than 20. 
</p>

<pre class="explanation">
TextExtents extents = cr.TextExtents("ZetCode");
</pre>

<p>
We get the text metrics. 
</p>

<pre class="explanation">
cr.MoveTo(x - extents.Width/2, y);
</pre>

<p>
We use the text metrics to center the text on the window. 
</p>

<pre class="explanation">
cr.TextPath("ZetCode");
cr.Clip();
</pre>

<p>
We get the path of the text and set the current clip region to it. 
</p>

<pre class="explanation">
cr.Stroke();
cr.PaintWithAlpha(alpha);
</pre>

<p>
We paint the current path and take alpha value into account. 
</p>

<img src="/img/gui/sharpgtk/puff.gif" alt="Puff">
<div class="figure">Figure: Puff</div>


<h2>Reflection</h2>

<p>
In the next example we show a reflected image. This beautiful effect makes an 
illusion as if the image was reflected in water. 
</p>

<div class="codehead">reflection.cs</div>
<pre class="code">
using Gtk;
using Cairo;
using System;
 
class SharpApp : Window {
 
    private ImageSurface surface;
    private int imageWidth;
    private int imageHeight;
    private int gap;
    private int border;

    public SharpApp() : base("Reflection")
    {
        
        try {
            surface = new ImageSurface("slanec.png");
        } catch {
            Console.WriteLine("File not found");
            Environment.Exit(1);
        } 
        
        imageWidth = surface.Width;
        imageHeight = surface.Height;
        gap = 40;
        border = 20;

        SetDefaultSize(300, 350);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };
        
        DrawingArea darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);
                   
        int width = Allocation.Width;
        int height = Allocation.Height;
          
        LinearGradient lg = new LinearGradient(width/2, 0, width/2, height*3);
        lg.AddColorStop(0, new Color(0, 0, 0, 1));
        lg.AddColorStop(height, new Color(0.2, 0.2, 0.2, 1));

        cr.Pattern = lg;
        cr.Paint();
        
        cr.SetSourceSurface(surface, border, border);
        cr.Paint();

        double alpha = 0.7;
        double step = 1.0 / imageHeight;
      
        cr.Translate(0, 2 * imageHeight + gap);
        cr.Scale(1, -1);
        
        int i = 0;
        
       
        while(i &lt; imageHeight) {
            cr.Rectangle(new Rectangle(border, imageHeight-i, imageWidth, 1));

            i++;
            
            cr.Clip();
            cr.SetSource(surface, border, border);

            cr.PaintWithAlpha(alpha-=step);
            cr.ResetClip();
        }
        
         
        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}
</pre>

<p>
The example shows a reflected castle. 
</p>

<pre class="explanation">
LinearGradient lg = new LinearGradient(width/2, 0, width/2, height*3);
lg.AddColorStop(0, new Color(0, 0, 0, 1));
lg.AddColorStop(height, new Color(0.2, 0.2, 0.2, 1));

cr.Pattern = lg;
cr.Paint();
</pre>

<p>
The background is filled with a gradiet paint. The paint is a smooth blending from black to dark gray. 
</p>

<pre class="explanation">
cr.Translate(0, 2 * imageHeight + gap);
cr.Scale(1, -1);
</pre>

<p>
This code flips the image and translates it below the original image. 
The translation operation is necessary, because the scaling operation makes 
the image upside down and translates the image up. To understand what happens, 
simply take a photograph and place it on the table. Now flip it.
</p>

<pre class="explanation">
cr.Rectangle(new Rectangle(border, imageHeight-i, imageWidth, 1));

i++;
        
cr.Clip();
cr.SetSource(surface, border, border);

cr.PaintWithAlpha(alpha-=step);
cr.ResetClip();
</pre>

<p>
Crucial part of the code. We make the second image transparent. 
But the transparency is not constant. The image gradually fades out. 
This is achieved with the <code>GradientPaint</code>. 
</p>

<img src="/img/gui/sharpgtk/reflection.png" alt="Reflection">
<div class="figure">Figure: Reflection</div>


<h2>Waiting</h2>

<p>
In this examle, we use transparency effect to create a waiting demo.
We will draw 8 lines that will gradually fade out creating an illusion,
that a line is moving. Such effects are often used to inform users, 
that a lengthy task is going on behind the scenes. 
An example is streaming video over the internet.  
</p>

<div class="codehead">waiting.cs</div>
<pre class="code">
using Gtk;
using Cairo;
using System;
 
class SharpApp : Window {
 

    private double [,] trs = new double[,] {
        { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
        { 1.0, 0.0,  0.15, 0.30, 0.5, 0.65, 0.8, 0.9 },
        { 0.9, 1.0,  0.0,  0.15, 0.3, 0.5, 0.65, 0.8 },
        { 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5, 0.65},
        { 0.65, 0.8, 0.9,  1.0,  0.0,  0.15, 0.3, 0.5 },
        { 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15, 0.3 },
        { 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0,  0.15 },
        { 0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0,  0.0, }
    };

    private short count = 0;
    private DrawingArea darea;

    public SharpApp() : base("Waiting")
    {
        SetDefaultSize(250, 150);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };
      
        GLib.Timeout.Add(100, new GLib.TimeoutHandler(OnTimer));
      
        darea = new DrawingArea();
        darea.ExposeEvent += OnExpose;

        Add(darea);

        ShowAll();
    }

    bool OnTimer() 
    { 
        count += 1;
        darea.QueueDraw();
        return true;
    }        

      
    void OnExpose(object sender, ExposeEventArgs args)
    {
        DrawingArea area = (DrawingArea) sender;
        Cairo.Context cr =  Gdk.CairoHelper.Create(area.GdkWindow);
        
        cr.LineWidth = 3;
        cr.LineCap = LineCap.Round;

        int width, height;
        width = Allocation.Width;
        height = Allocation.Height;

        cr.Translate(width/2, height/2);

        for (int i = 0; i < 8; i++) {
            cr.SetSourceRGBA(0, 0, 0, trs[count%8, i]);
            cr.MoveTo(0.0, -10.0);
            cr.LineTo(0.0, -40.0);
            cr.Rotate(Math.PI/4);
            cr.Stroke();
        }

        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();
    }

    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}
</pre>

<p>
We draw eight lines with eight different alpha values. 
</p>

<pre class="explanation">
GLib.Timeout.Add(100, new GLib.TimeoutHandler(OnTimer));
</pre>

<p>
We use a timer function to create animation. 
</p>


<pre class="explanation">
private double [,] trs = new double[,] {
    { 0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0 },
    ...
};
</pre>

<p>
This is a two dimensional array of transparency values used in this demo. 
There are 8 rows, each for one state. Each of the 8 lines will continuosly use these values. 
</p>

<pre class="explanation">
cr.LineWidth = 3;
cr.LineCap = LineCap.Round;
</pre>

<p>
We make the lines a bit thicker, so that they are better visible. 
We draw the lines with rouded caps. 
</p>

<pre class="explanation">
cr.SetSourceRGBA(0, 0, 0, trs[count%8, i]);
</pre>

<p>
Here we define the transparency value for a line. 
</p>

<pre class="explanation">
cr.MoveTo(0.0, -10.0);
cr.LineTo(0.0, -40.0);
cr.Rotate(Math.PI/4);
cr.Stroke();
</pre>

<p>
These code lines will draw each of the eight lines. 
</p>


<img src="/img/gui/sharpgtk/waiting.png" alt="Waiting">
<div class="figure">Figure: Waiting</div>


<p>
In this chapter of the GTK# programming library, we did some
more advanced drawing with the Cairo library.
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>
<br>


<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified January 27, 2009  <span class="copyright">&copy; 2007 - 2013 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>

